// This is an open source non-commercial project. Dear PVS-Studio, please check it.
// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com

// ReSharper disable CheckNamespace
// ReSharper disable CommentTypo
// ReSharper disable InconsistentNaming
// ReSharper disable UnusedMember.Global

/* PieItem.cs --
 * Ars Magna project, http://arsmagna.ru
 */

#region Using directives

using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Globalization;
using System.Runtime.Serialization;

#endregion

#nullable enable

namespace AM.Drawing.Charting;

/// <summary>
/// A class representing a pie chart object comprised of one or more
/// <see cref="PieItem"/>s.
/// </summary>
[Serializable]
public class PieItem
    : CurveItem, ICloneable
{
    #region Fields

    /*
    /// <summary>
    /// Private field instance of the <see cref="PieItem"/> class indicating whether
    /// the instance is displayed in 2D or 3D.(see <see cref="PieItem.PieType"/>)
    /// </summary>
    private PieType pieType;
*/
    /// <summary>
    /// Percentage (expressed as #.##) of <see cref="PieItem"/>	radius  to
    /// which this <see cref="PieItem"/> is to be displaced from the center.
    ///   Displacement is done outward  along the radius
    /// bisecting the chord of this <see cref="PieItem"/>.  Maximum allowable value
    /// is 0.5.
    /// </summary>
    private double _displacement;

    /// <summary>
    /// A <see cref="TextObj"/> which will customize the label display of this
    /// <see cref="PieItem"/>
    /// </summary>
    private TextObj? _labelDetail;

    /// <summary>
    /// Private	field	that stores the	<see cref="Charting.Fill"/> data for this
    /// <see	cref="PieItem"/>.	 Use the public property <see	cref="Fill"/> to
    /// access this value.
    /// </summary>
    private Fill _fill;

    /// <summary>
    /// Private	field	that stores the	<see cref="Border"/> class that defines	the
    /// properties of the	border around	this <see cref="PieItem"/>. Use the public
    /// property	<see cref="Border"/> to access this value.
    /// </summary>
    private Border _border;

    /// <summary>
    /// Private field that stores the absolute value of this <see cref="PieItem"/> instance.
    /// Value will be set to zero if submitted value is less than zero.
    /// </summary>
    private double _pieValue;

    /// <summary>
    /// An enum that specifies how each <see cref="CurveItem.Label"/> for this <see cref="PieItem"/> object
    /// will be displayed.  Use the public property <see cref="LabelType"/> to access this data.
    /// Use enum <see cref="PieLabelType"/>.
    /// </summary>
    private PieLabelType _labelType;

    /// <summary>
    /// The point on the arc of this <see cref="PieItem"/> representing the intersection of
    /// the arc and the explosion radius.
    /// </summary>
    private PointF _intersectionPoint;

    /// <summary>
    /// The bounding rectangle for this <see cref="PieItem"/>.
    /// </summary>
    private RectangleF _boundingRectangle;

    /// <summary>
    /// The formatted string for this <see cref="PieItem"/>'s label.  Formatting is
    /// done based on  the <see cref="PieLabelType"/>.
    /// </summary>
    private string _labelStr;

    /// <summary>
    /// The point at which the line between this <see cref="PieItem"/> and its
    /// label bends to the horizontal.
    /// </summary>
    private PointF _pivotPoint;

    /// <summary>
    /// The point at the end of the line between	this <see cref="PieItem"/> and
    /// it's label (i.e. the beginning of the label display)
    /// </summary>
    private PointF _endPoint;

    /// <summary>
    /// Private field which holds the angle (in degrees) at which the display of this <see cref="PieItem"/>
    /// object will begin.
    /// </summary>
    private float _startAngle;

    /// <summary>
    ///Private field which holds the length (in degrees) of the arc representing this <see cref="PieItem"/>
    ///object.
    /// </summary>
    private float _sweepAngle;

    /// <summary>
    ///Private field which represents the angle (in degrees) of the radius along which this <see cref="PieItem"/>
    ///object will be displaced, if desired.
    /// </summary>
    private float _midAngle;

    /// <summary>
    ///Private field which determines the number of decimal digits displayed to
    ///in a <see cref="PieItem"/> label containing a value.
    /// </summary>
    private int _valueDecimalDigits;

    /// <summary>
    ///Private field which determines the number of decimal digits displayed
    ///in a <see cref="PieItem"/> label containing a percent.
    /// </summary>
    private int _percentDecimalDigits;

    private static ColorSymbolRotator _rotator = new ColorSymbolRotator();

    #endregion

    #region Defaults

    /// <summary>
    /// Specify the default property values for the <see cref="PieItem"/> class.
    /// </summary>
    public struct Default
    {
        /// <summary>
        ///Default <see cref="PieItem "/> displacement.
        /// </summary>
        public static double Displacement = 0;

        /// <summary>
        /// The default pen width	to be used for drawing the	border around	the PieItem
        /// (<see cref="LineBase.Width"/> property). Units are points.
        /// </summary>
        public static float BorderWidth = 1.0F;

        /// <summary>
        /// The default fill mode for this PieItem (<see	cref="Fill.Type"/> property).
        /// </summary>
        public static FillType FillType = FillType.Brush;

        /// <summary>
        /// The default border mode for PieItem (<see cref="LineBase.IsVisible"/> property).
        /// true to	display frame around PieItem, false otherwise
        /// </summary>
        public static bool IsBorderVisible = true;

        /// <summary>
        /// The default color for drawing	frames around	PieItem
        /// (<see cref="LineBase.Color"/> property).
        /// </summary>
        public static Color BorderColor = Color.Black;

        /// <summary>
        /// The default color for filling in	the PieItem
        /// (<see cref="Fill.Color"/>	property).
        /// </summary>
        public static Color FillColor = Color.Red;

        /// <summary>
        /// The default custom brush for filling in the PieItem.
        /// (<see cref="Fill.Brush"/> property).
        /// </summary>
        public static Brush? FillBrush = null;

        /// <summary>
        ///Default value for controlling <see cref="PieItem"/> display.
        /// </summary>
        public static bool isVisible = true;

        /// <summary>
        /// Default value for <see cref="PieItem.LabelType"/>.
        /// </summary>
        public static PieLabelType LabelType = PieLabelType.Name;

        /// <summary>
        /// The default font size for  <see cref="PieItem.LabelDetail"/> entries
        /// (<see cref="FontSpec.Size"/> property).  Units are
        /// in points (1/72 inch).
        /// </summary>
        public static float FontSize = 10;

        /// <summary>
        /// Default value for the number of decimal digits
        /// to be displayed when <see cref="LabelType"/>  contains a value.
        /// </summary>
        public static int ValueDecimalDigits = 0;

        /// <summary>
        /// Default value for the number of decimal digits
        /// to be displayed where <see cref="LabelType"/> contains a percent.
        /// </summary>
        public static int PercentDecimalDigits = 2;
    }

    #endregion Defaults

    #region PieItem Properties

    /// <summary>
    /// Gets or sets the a value which determines the amount, if any, of this <see cref="PieItem"/>
    /// displacement.
    /// </summary>
    public double Displacement
    {
        get => _displacement;
        set => _displacement = value > .5 ? .5 : value;
    }

    /// <summary>
    /// Gets a path representing this <see cref="PieItem"/>
    /// </summary>
    public GraphicsPath? SlicePath { get; private set; }

    /// <summary>
    /// Gets or sets the <see cref="TextObj"/> to be used
    /// for displaying this <see cref="PieItem"/>'s label.
    /// </summary>
    public TextObj? LabelDetail
    {
        get => _labelDetail;
        set => _labelDetail = value;
    }

    /// <summary>
    /// Gets or sets the <see cref="Border"/> object so as to be able to modify
    /// its properties.
    /// </summary>
    public Border Border
    {
        get => _border;
        set => _border = value;
    }

    /// <summary>
    /// Gets or sets the <see cref="Fill" /> object which is used to fill the
    /// pie slice with color.
    /// </summary>
    public Fill Fill
    {
        get => _fill;
        set => _fill = value;
    }

    /// <summary>
    /// Gets or sets the arc length (in degrees) of this <see cref="PieItem"/>.
    /// </summary>
    private float SweepAngle
    {
        get => _sweepAngle;
        set => _sweepAngle = value;
    }

    /// <summary>
    /// Gets or sets the starting angle (in degrees) of this <see cref="PieItem"/>.
    /// </summary>
    private float StartAngle
    {
        get => _startAngle;
        set => _startAngle = value;
    }

    /// <summary>
    /// Gets or sets the angle (in degrees) of the radius along which
    /// this <see cref="PieItem"/> will be displaced.
    /// </summary>
    private float MidAngle
    {
        get => _midAngle;
        set => _midAngle = value;
    }

    /// <summary>
    ///  Gets or sets the value of this <see cref="PieItem"/>.
    ///  Minimum value is 0.
    /// </summary>
    public double Value
    {
        get => _pieValue;
        set => _pieValue = value > 0 ? value : 0;
    }

    /// <summary>
    /// Gets or sets the <see cref="PieLabelType"/> to be used in displaying
    /// <see cref="PieItem"/> labels.
    /// </summary>
    public PieLabelType LabelType
    {
        get => _labelType;
        set
        {
            _labelType = value;
            if (value == PieLabelType.None)
            {
                LabelDetail!.IsVisible = false;
            }
            else
            {
                LabelDetail!.IsVisible = true;
            }
        }
    }

    /// <summary>
    /// Gets or sets the number of decimal digits to be displayed in a <see cref="PieItem"/>
    /// value label.
    /// </summary>
    public int ValueDecimalDigits
    {
        get => _valueDecimalDigits;
        set => _valueDecimalDigits = value;
    }

    /// <summary>
    /// Gets or sets the number of decimal digits to be displayed in a <see cref="PieItem"/>
    /// percent label.
    /// </summary>
    public int PercentDecimalDigits
    {
        get => _percentDecimalDigits;
        set => _percentDecimalDigits = value;
    }

    /*
    /// <summary>
    /// Getsor sets enum <see cref="PieType"/> to be used	for drawing this <see cref="PieItem"/>.
    /// </summary>
    public PieType PieType
    {
        get { return (this.pieType); }
        set { this.pieType = value; }
    }
*/

    /// <inheritdoc cref="CurveItem.IsZIncluded"/>
    internal override bool IsZIncluded (GraphPane pane)
    {
        return false;
    }

    /// <inheritdoc cref="CurveItem.IsXIndependent"/>
    internal override bool IsXIndependent (GraphPane pane)
    {
        return true;
    }

    #endregion

    #region Constructors

    /// <summary>
    /// Create a new <see cref="PieItem"/>, providing a gradient fill for the pie color.
    /// </summary>
    /// <param name="pieValue">The value associated with this <see cref="PieItem"/> instance.</param>
    /// <param name="color1">The starting display color for the gradient <see cref="Fill"/> for this
    /// <see cref="PieItem"/> instance.</param>
    /// <param name="color2">The ending display color for the gradient <see cref="Fill"/> for this
    /// <see cref="PieItem"/> instance.</param>
    /// <param name="fillAngle">The angle for the gradient <see cref="Fill"/>.</param>
    /// <param name="displacement">The amount this <see cref="PieItem"/>  instance will be
    /// displaced from the center point.</param>
    /// <param name="label">Text label for this <see cref="PieItem"/> instance.</param>
    public PieItem
        (
            double pieValue,
            Color color1,
            Color color2,
            float fillAngle,
            double displacement,
            string label
        )
        :
        this (pieValue, color1, displacement, label)
    {
        if (!color1.IsEmpty && !color2.IsEmpty)
        {
            _fill = new Fill (color1, color2, fillAngle);
        }
    }

    /// <summary>
    /// Create a new <see cref="PieItem"/>.
    /// </summary>
    /// <param name="pieValue">The value associated with this <see cref="PieItem"/> instance.</param>
    /// <param name="color">The display color for this <see cref="PieItem"/> instance.</param>
    /// <param name="displacement">The amount this <see cref="PieItem"/>  instance will be
    /// displaced from the center point.</param>
    /// <param name="label">Text label for this <see cref="PieItem"/> instance.</param>
    public PieItem
        (
            double pieValue,
            Color color,
            double displacement,
            string label
        )
        : base (label)
    {
        _labelStr = null!;

        _pieValue = pieValue;
        _fill = new Fill (color.IsEmpty ? _rotator.NextColor : color);
        _displacement = displacement;
        _border = new Border (Default.BorderColor, Default.BorderWidth);
        _labelDetail = new TextObj
        {
            FontSpec =
            {
                Size = Default.FontSize
            }
        };
        _labelType = Default.LabelType;
        _valueDecimalDigits = Default.ValueDecimalDigits;
        _percentDecimalDigits = Default.PercentDecimalDigits;
        SlicePath = null;
    }

    /// <summary>
    /// Create a  new <see cref="PieItem"/>.
    /// </summary>
    /// <param name="pieValue">The value associated with this <see cref="PieItem"/> instance.</param>
    /// <param name="label">Text label for this <see cref="PieItem"/> instance</param>
    public PieItem (double pieValue, string label)
        :
        this (pieValue, _rotator.NextColor, Default.Displacement, label)
    {
    }

    /// <summary>
    /// The Copy Constructor
    /// </summary>
    /// <param name="rhs">The <see cref="PieItem"/> object from which to copy</param>
    public PieItem (PieItem rhs)
        : base (rhs)
    {
        _labelStr = null!;
        _border = null!;

        _pieValue = rhs._pieValue;
        _fill = rhs._fill.Clone();
        Border = rhs._border.Clone();
        _displacement = rhs._displacement;
        _labelDetail = rhs._labelDetail!.Clone();
        _labelType = rhs._labelType;
        _valueDecimalDigits = rhs._valueDecimalDigits;
        _percentDecimalDigits = rhs._percentDecimalDigits;
    }

    /// <summary>
    /// Implement the <see cref="ICloneable" /> interface in a typesafe manner by just
    /// calling the typed version of <see cref="Clone" />
    /// </summary>
    /// <returns>A deep copy of this object</returns>
    object ICloneable.Clone()
    {
        return Clone();
    }

    /// <summary>
    /// Typesafe, deep-copy clone method.
    /// </summary>
    /// <returns>A new, independent copy of this class</returns>
    public PieItem Clone()
    {
        return new PieItem (this);
    }

    #endregion

    #region Serialization

    /// <summary>
    /// Current schema value that defines the version of the serialized file
    /// </summary>
    public const int schema2 = 10;

    /// <summary>
    /// Constructor for deserializing objects
    /// </summary>
    /// <param name="info">A <see cref="SerializationInfo"/> instance that defines the serialized data
    /// </param>
    /// <param name="context">A <see cref="StreamingContext"/> instance that contains the serialized data
    /// </param>
    protected PieItem
        (
            SerializationInfo info,
            StreamingContext context
        )
        : base (info, context)
    {
        // The schema value is just a file version parameter.  You can use it to make future versions
        // backwards compatible as new member variables are added to classes
        info.GetInt32 ("schema2").NotUsed();

        _displacement = info.GetDouble ("displacement");
        _labelDetail = (TextObj) info.GetValue ("labelDetail", typeof (TextObj))!;
        _fill = (Fill)info.GetValue ("fill", typeof (Fill))!;
        _border = (Border)info.GetValue ("border", typeof (Border))!;
        _pieValue = info.GetDouble ("pieValue");
        _labelType = (PieLabelType)info.GetValue ("labelType", typeof (PieLabelType))!;
        _intersectionPoint = (PointF)info.GetValue ("intersectionPoint", typeof (PointF))!;
        _boundingRectangle = (RectangleF)info.GetValue ("boundingRectangle", typeof (RectangleF))!;
        _pivotPoint = (PointF)info.GetValue ("pivotPoint", typeof (PointF))!;
        _endPoint = (PointF)info.GetValue ("endPoint", typeof (PointF))!;

        // _slicePath = (GraphicsPath)info.GetValue( "slicePath", typeof( GraphicsPath ) );
        _startAngle = (float)info.GetDouble ("startAngle");
        _sweepAngle = (float)info.GetDouble ("sweepAngle");
        _midAngle = (float)info.GetDouble ("midAngle");
        _labelStr = info.GetString ("labelStr")!;
        _valueDecimalDigits = info.GetInt32 ("valueDecimalDigits");
        _percentDecimalDigits = info.GetInt32 ("percentDecimalDigits");
    }

    /// <inheritdoc cref="ISerializable.GetObjectData"/>
    public override void GetObjectData
        (
            SerializationInfo info,
            StreamingContext context
        )
    {
        base.GetObjectData (info, context);
        info.AddValue ("schema2", schema2);
        info.AddValue ("displacement", _displacement);
        info.AddValue ("labelDetail", _labelDetail);
        info.AddValue ("fill", _fill);
        info.AddValue ("border", _border);
        info.AddValue ("pieValue", _pieValue);
        info.AddValue ("labelType", _labelType);
        info.AddValue ("intersectionPoint", _intersectionPoint);
        info.AddValue ("boundingRectangle", _boundingRectangle);
        info.AddValue ("pivotPoint", _pivotPoint);
        info.AddValue ("endPoint", _endPoint);

        // info.AddValue( "slicePath", _slicePath );
        info.AddValue ("startAngle", _startAngle);
        info.AddValue ("sweepAngle", _sweepAngle);
        info.AddValue ("midAngle", _midAngle);
        info.AddValue ("labelStr", _labelStr);
        info.AddValue ("valueDecimalDigits", _valueDecimalDigits);
        info.AddValue ("percentDecimalDigits", _percentDecimalDigits);
    }

    #endregion

    #region Methods

    /// <inheritdoc cref="CurveItem.Draw"/>
    public override void Draw
        (
            Graphics graphics,
            GraphPane pane,
            int pos,
            float scaleFactor
        )
    {
        if (pane.Chart._rect is { Width: <= 0, Height: <= 0 })
        {
            //pane.PieRect = RectangleF.Empty;
            SlicePath = null;
        }
        else
        {
            //pane.PieRect = CalcPieRect( g, pane, scaleFactor, pane.ChartRect );
            CalcPieRect (graphics, pane, scaleFactor, pane.Chart._rect);

            SlicePath = new GraphicsPath();

            if (!IsVisible)
            {
                return;
            }

            var tRect = _boundingRectangle;

            if (tRect is { Width: >= 1, Height: >= 1 })
            {
                var sMode = graphics.SmoothingMode;
                graphics.SmoothingMode = SmoothingMode.AntiAlias;

                var tFill = _fill;
                var tBorder = _border;
                if (IsSelected)
                {
                    tFill = Selection.Fill;
                    tBorder = Selection.Border;
                }

                using (var brush = tFill.MakeBrush (_boundingRectangle))
                {
                    graphics.FillPie (brush, tRect.X, tRect.Y, tRect.Width, tRect.Height, StartAngle,
                        SweepAngle);

                    //add GraphicsPath for hit testing
                    SlicePath.AddPie (tRect.X, tRect.Y, tRect.Width, tRect.Height,
                        StartAngle, SweepAngle);

                    if (Border.IsVisible)
                    {
                        using (var borderPen = tBorder.GetPen (pane, scaleFactor))
                        {
                            graphics.DrawPie (borderPen, tRect.X, tRect.Y, tRect.Width, tRect.Height,
                                StartAngle, SweepAngle);
                        }
                    }

                    if (_labelType != PieLabelType.None)
                    {
                        DrawLabel (graphics, pane, tRect, scaleFactor);
                    }

                    //brush.Dispose();
                }

                graphics.SmoothingMode = sMode;
            }
        }
    }

    /// <summary>
    /// Calculate the <see cref="RectangleF"/> that will be used to define the bounding rectangle of
    /// the Pie.
    /// </summary>
    /// <remarks>This rectangle always lies inside of the <see cref="Chart.Rect"/>, and it is
    /// normally a square so that the pie itself is not oval-shaped.</remarks>
    /// <param name="g">
    /// A graphic device object to be drawn into.  This is normally e.Graphics from the
    /// PaintEventArgs argument to the Paint() method.
    /// </param>
    /// <param name="pane">
    /// A reference to the <see cref="GraphPane"/> object that is the parent or
    /// owner of this object.
    /// </param>
    /// <param name="scaleFactor">
    /// The scaling factor to be used for rendering objects.  This is calculated and
    /// passed down by the parent <see cref="GraphPane"/> object using the
    /// <see cref="PaneBase.CalcScaleFactor"/> method, and is used to proportionally adjust
    /// font sizes, etc. according to the actual size of the graph.
    /// </param>
    /// <param name="chartRect">The <see cref="RectangleF"/> (normally the <see cref="Chart.Rect"/>)
    /// that bounds this pie.</param>
    /// <returns></returns>
    public static RectangleF CalcPieRect (Graphics g, GraphPane pane, float scaleFactor, RectangleF chartRect)
    {
        //want to draw the largest pie possible within ChartRect
        //but want to leave  5% slack around the pie so labels will not overrun clip area
        //largest pie is limited by the smaller of ChartRect.height or ChartRect.width...
        //this rect (nonExplRect)has to be re-positioned so that it's in the center of ChartRect.
        //Where ChartRect is almost a square - low Aspect Ratio -, need to contract pieRect so that there's some
        //room for labels, if they're visible.
        double maxDisplacement = 0;

        var nonExplRect = chartRect;

        if (pane.CurveList.IsPieOnly)
        {
            if (nonExplRect.Width < nonExplRect.Height)
            {
                //create slack rect
                nonExplRect.Inflate (-(float)0.05F * nonExplRect.Height, -(float)0.05F * nonExplRect.Width);

                //get the difference between dimensions
                var delta = (nonExplRect.Height - nonExplRect.Width) / 2;

                //make a square	so we end up with circular pie
                nonExplRect.Height = nonExplRect.Width;

                //keep the center point  the same
                nonExplRect.Y += delta;
            }
            else
            {
                nonExplRect.Inflate (-(float)0.05F * nonExplRect.Height, -(float)0.05F * nonExplRect.Width);
                var delta = (nonExplRect.Width - nonExplRect.Height) / 2;
                nonExplRect.Width = nonExplRect.Height;
                nonExplRect.X += delta;
            }

            //check aspect ratio
            double aspectRatio = chartRect.Width / chartRect.Height;

            //make an adjustment in rect size,as aspect ratio varies
            if (aspectRatio < 1.5)
            {
                nonExplRect.Inflate (-(float)(.1 * (1.5 / aspectRatio) * nonExplRect.Width),
                    -(float)(.1 * (1.5 / aspectRatio) * nonExplRect.Width));
            }

            //modify the rect to determine if any of the labels need to be wrapped....
            //first see if there's any exploded slices and if so, what's the max displacement...
            //also, might as well get all the display params we can
            CalculatePieChartParams (pane, ref maxDisplacement);

            if (maxDisplacement != 0) //need new rectangle if any slice exploded
            {
                CalcNewBaseRect (maxDisplacement, ref nonExplRect);
            }

            foreach (PieItem slice in pane.CurveList)
            {
                slice._boundingRectangle = nonExplRect;

                //if exploded, need to re-calculate rectangle for slice
                if (slice.Displacement != 0)
                {
                    var tempRect = nonExplRect; //= new RectangleF(0,0,0,0);
                    slice.CalcExplodedRect (ref tempRect);
                    slice._boundingRectangle = tempRect;
                }

                //now get all the other slice specific drawing details, including need for wrapping label
                slice.DesignLabel (g, pane, slice._boundingRectangle, scaleFactor);
            }
        }

        return nonExplRect;
    }

    /// <summary>
    /// Recalculate the bounding rectangle when a piee slice is displaced.
    /// </summary>
    /// <param name="explRect">rectangle to be used for drawing exploded pie</param>
    private void CalcExplodedRect (ref RectangleF explRect)
    {
        //pie exploded out along the slice bisector - modify upper left of bounding rect to account for displacement
        //keep height and width same
        explRect.X += (float)(Displacement * explRect.Width / 2 * Math.Cos (_midAngle * Math.PI / 180));
        explRect.Y += (float)(Displacement * explRect.Height / 2 * Math.Sin (_midAngle * Math.PI / 180));
    }

    /// <summary>
    /// Calculate the values needed to properly display this <see cref="PieItem"/>.
    /// </summary>
    /// <param name="pane">
    /// A graphic device object to be drawn into.  This is normally e.Graphics from the
    /// PaintEventArgs argument to the Paint() method.
    /// </param>
    /// <param name="maxDisplacement">maximum slice displacement</param>
    private static void CalculatePieChartParams (GraphPane pane, ref double maxDisplacement)
    {
        //loop thru slices and get total value and maxDisplacement
        double pieTotalValue = 0;
        foreach (PieItem curve in pane.CurveList)
            if (curve.IsPie)
            {
                pieTotalValue += curve._pieValue;
                if (curve.Displacement > maxDisplacement)
                {
                    maxDisplacement = curve.Displacement;
                }
            }

        double nextStartAngle = 0;

        //now loop thru and calculate the various angle values
        foreach (PieItem curve in pane.CurveList)
        {
            curve.StartAngle = (float)nextStartAngle;
            curve.SweepAngle = (float)(360 * curve.Value / pieTotalValue);
            curve.MidAngle = curve.StartAngle + curve.SweepAngle / 2;
            nextStartAngle = curve._startAngle + curve._sweepAngle;
            BuildLabelString (curve);
        }
    }

    /// <summary>
    /// Render the label for this <see cref="PieItem"/>.
    /// </summary>
    /// <param name="g">
    /// A graphic device object to be drawn into.  This is normally e.Graphics from the
    /// PaintEventArgs argument to the Paint() method.
    /// </param>
    /// <param name="pane">
    /// A graphic device object to be drawn into.  This is normally e.Graphics from the
    /// PaintEventArgs argument to the Paint() method.
    /// </param>
    /// <param name="rect">Bounding rectangle for this <see cref="PieItem"/>.</param>
    /// <param name="scaleFactor">
    /// The scaling factor to be used for rendering objects.  This is calculated and
    /// passed down by the parent <see cref="GraphPane"/> object using the
    /// <see cref="PaneBase.CalcScaleFactor"/> method, and is used to proportionally adjust
    /// font sizes, etc. according to the actual size of the graph.
    /// </param>
    public void DrawLabel (Graphics g, GraphPane pane, RectangleF rect, float scaleFactor)
    {
        if (!_labelDetail!.IsVisible)
        {
            return;
        }

        using (var labelPen = Border.GetPen (pane, scaleFactor))
        {
            //draw line from intersection point to pivot point -
            g.DrawLine (labelPen, _intersectionPoint, _pivotPoint);

            //draw horizontal line to move label away from pie...
            g.DrawLine (labelPen, _pivotPoint, _endPoint);
        }

        //draw the label (TextObj)
        _labelDetail.Draw (g, pane, scaleFactor);
    }

    /// <summary>
    /// This method collects all the data relative to rendering this <see cref="PieItem"/>'s label.
    /// </summary>
    /// <param name="g">
    ///  A graphic device object to be drawn into.  This is normally e.Graphics from the
    /// PaintEventArgs argument to the Paint() method.
    /// </param>
    /// <param name="pane">
    /// A graphic device object to be drawn into.  This is normally e.Graphics from the
    /// PaintEventArgs argument to the Paint() method.
    /// </param>
    /// <param name="rect">The rectangle used for rendering this <see cref="PieItem"/>
    /// </param>
    /// <param name="scaleFactor">
    /// The scaling factor to be used for rendering objects.  This is calculated and
    /// passed down by the parent <see cref="GraphPane"/> object using the
    /// <see cref="PaneBase.CalcScaleFactor"/> method, and is used to proportionally adjust
    /// font sizes, etc. according to the actual size of the graph.
    /// </param>
    public void DesignLabel (Graphics g, GraphPane pane, RectangleF rect, float scaleFactor)
    {
        if (!_labelDetail!.IsVisible)
        {
            return;
        }

        _labelDetail.LayoutArea = new SizeF();

        //this.labelDetail.IsWrapped = false;

        //label line will come off the explosion radius and then pivot to the horizontal right or left,
        //dependent on position..
        //text will be at the end of horizontal segment...
        CalculateLinePoints (rect, _midAngle);

        //now get size of bounding rect for label
        var size = _labelDetail.FontSpec.BoundingBox (g, _labelStr, scaleFactor);

        //how much room left for the label - most likely midangles for wrapping
        //Right - 315 -> 45 degrees
        //Bottom - 45 -> 135
        //Left - 135 -> 225
        //Top - 225 -> 315
        var chartRect = pane.Chart._rect;
        float fill;
        if (_midAngle > 315 || _midAngle <= 45)
        {
            //correct by wrapping text
            fill = chartRect.X + chartRect.Width - _endPoint.X - 5;
            if (size.Width > fill)
            {
                //need to wrap, so create label rectangle for overloaded DrawString - two rows, max
                _labelDetail.LayoutArea = new SizeF (fill, size.Height * 3.0F);
            }
        }

        if (_midAngle is > 45 and <= 135)
        {
            //correct by moving radial line toward one or the other end of the range
            fill = chartRect.Y + chartRect.Height - _endPoint.Y - 5;

            //is there enuf room for the label
            if (size.Height / 2 > fill)
            {
                //no, so got to move explosion radius
                if (_midAngle > 90) //move _label clockwise one-third of way to the end of the arc
                {
                    CalculateLinePoints (rect, _midAngle + (_sweepAngle + _startAngle - _midAngle) / 3);
                }
                else //move _label counter-clockwise one-third of way to the start of the arc
                {
                    CalculateLinePoints (rect, _midAngle - (_midAngle - (_midAngle - _startAngle) / 3));
                }
            }
        }

        if (_midAngle is > 135 and <= 225)
        {
            //wrap text
            fill = _endPoint.X - chartRect.X - 5;

            //need to wrap, so create label rectangle for overloaded DrawString - two rows, max
            if (size.Width > fill)
            {
                _labelDetail.LayoutArea = new SizeF (fill, size.Height * 3.0F);
            }
        }

        if (_midAngle is > 225 and <= 315)
        {
            //correct by moving radial line toward one or the other end of the range
            fill = _endPoint.Y - 5 - chartRect.Y;

            //is there enuf room for the label
            if (size.Height / 2 > fill)
            {
                //no, so got to move explosion radius
                if (_midAngle < 270) //move _label counter-clockwise one-third of way to the start of the arc
                {
                    CalculateLinePoints (rect, _midAngle - (_sweepAngle + _startAngle - _midAngle) / 3);
                }
                else //move _label clockwise one-third of way to the end of the arc
                {
                    CalculateLinePoints (rect, _midAngle + (_midAngle - _startAngle) / 3);
                }
            }
        }

        //complete the location Detail info
        _labelDetail.Location.AlignV = AlignV.Center;
        _labelDetail.Location.CoordinateFrame = CoordType.PaneFraction;
        _labelDetail.Location.X = (_endPoint.X - pane.Rect.X) / pane.Rect.Width;
        _labelDetail.Location.Y = (_endPoint.Y - pane.Rect.Y) / pane.Rect.Height;
        _labelDetail.Text = _labelStr;
    }

    /// <summary>
    ///
    /// </summary>
    /// <param name="rect"></param>
    /// <param name="midAngle"></param>
    private void CalculateLinePoints (RectangleF rect, double midAngle)
    {
        //get the point where the explosion radius intersects the this arc
        var rectCenter = new PointF (rect.X + rect.Width / 2, rect.Y + rect.Height / 2);

        _intersectionPoint = new PointF (
            (float)(rectCenter.X + rect.Width / 2 * Math.Cos (midAngle * Math.PI / 180)),
            (float)(rectCenter.Y + rect.Height / 2 * Math.Sin (midAngle * Math.PI / 180)));

        //draw line from intersection point to pivot point - length to be .05 * pieRect.Width pixels long
        _pivotPoint = new PointF (
            (float)(_intersectionPoint.X + .05 * rect.Width * Math.Cos (midAngle * Math.PI / 180)),
            (float)(_intersectionPoint.Y + .05 * rect.Width * Math.Sin (midAngle * Math.PI / 180)));

        //add horizontal line to move label away from pie...length to be 5% of rect.Width
        //does line go to left or right....label alignment is to the opposite
        if (_pivotPoint.X >= rectCenter.X) //goes to right
        {
            _endPoint = new PointF ((float)(_pivotPoint.X + .05 * rect.Width), _pivotPoint.Y);
            _labelDetail!.Location.AlignH = AlignH.Left;
        }
        else
        {
            _endPoint = new PointF ((float)(_pivotPoint.X - .05 * rect.Width), _pivotPoint.Y);
            _labelDetail!.Location.AlignH = AlignH.Right;
        }

        _midAngle = (float)midAngle;
    }

    /// <summary>
    /// Build the string that will be displayed as the slice label as determined by
    /// <see cref="LabelType"/>.
    /// </summary>
    /// <param name="curve">reference to the <see cref="PieItem"/></param>
    private static void BuildLabelString (PieItem curve)
    {
        //set up label string formatting
        var labelFormat = (NumberFormatInfo)NumberFormatInfo.CurrentInfo.Clone();

        labelFormat.NumberDecimalDigits = curve._valueDecimalDigits;
        labelFormat.PercentPositivePattern = 1; //no space between number and % sign
        labelFormat.PercentDecimalDigits = curve._percentDecimalDigits;

        switch (curve._labelType)
        {
            case PieLabelType.Value:
                curve._labelStr = curve._pieValue.ToString ("F", labelFormat);
                break;

            case PieLabelType.Percent:
                curve._labelStr = (curve._sweepAngle / 360).ToString ("P", labelFormat);
                break;

            case PieLabelType.Name_Value:
                curve._labelStr = curve.Label!.Text + ": " + curve._pieValue.ToString ("F", labelFormat);
                break;

            case PieLabelType.Name_Percent:
                curve._labelStr = curve.Label!.Text + ": " + (curve._sweepAngle / 360).ToString ("P", labelFormat);
                break;

            case PieLabelType.Name_Value_Percent:
                curve._labelStr = curve.Label!.Text + ": " + curve._pieValue.ToString ("F", labelFormat) +
                                  " (" + (curve._sweepAngle / 360).ToString ("P", labelFormat) + ")";
                break;

            case PieLabelType.Name:
                curve._labelStr = curve.Label!.Text;
                break;

            case PieLabelType.None:
            default:
                break;
        }
    }

    /// <summary>
    /// A method which calculates a new size for the bounding rectangle for the non-displaced
    /// <see cref="PieItem"/>'s in the pie chart.  This method is called after it is found
    /// that at least one slice is displaced.
    /// </summary>
    /// <param name="maxDisplacement">The biggest displacement among the <see cref="PieItem"/>s
    /// making up the pie chart.</param>
    /// <param name="baseRect">The current bounding rectangle</param>
    private static void CalcNewBaseRect (double maxDisplacement, ref RectangleF baseRect)
    {
        //displacement expressed in terms of % of pie radius	...do not want exploded slice to
        //go beyond nonExplRect, but want to maintain the same center point...therefore, got to
        //reduce the diameter of the nonexploded pie by the alue of the displacement

        var xDispl = (float)(maxDisplacement * baseRect.Width);
        var yDispl = (float)(maxDisplacement * baseRect.Height);

        baseRect.Inflate (-(float)(xDispl / 10), -(float)(xDispl / 10));
    }

    /// <inheritdoc cref="CurveItem.DrawLegendKey"/>
    public override void DrawLegendKey
        (
            Graphics graphics,
            GraphPane pane,
            RectangleF rect,
            float scaleFactor
        )
    {
        if (!IsVisible)
        {
            return;
        }

        // Fill the slice
        if (_fill.IsVisible)
        {
            // just avoid height/width being less than 0.1 so GDI+ doesn't cry
            using (var brush = _fill.MakeBrush (rect))
            {
                graphics.FillRectangle (brush, rect);

                //brush.Dispose();
            }
        }

        // Border the bar
        if (!_border.Color.IsEmpty)
        {
            _border.Draw (graphics, pane, scaleFactor, rect);
        }
    }

    /// <inheritdoc cref="CurveItem.GetCoords"/>
    public override bool GetCoords
        (
            GraphPane pane,
            int i,
            out string coords
        )
    {
        coords = string.Empty;

        var pt = _boundingRectangle.Location;
        pt.X += _boundingRectangle.Width / 2.0f;
        pt.Y += _boundingRectangle.Height / 2.0f;

        var radius = _boundingRectangle.Width / 2.0f;
        var matrix = new Matrix();

        // Move the coordinate system to local coordinates
        // of this text object (that is, at the specified
        // x,y location)
        matrix.Translate (pt.X, pt.Y);

        matrix.Rotate (StartAngle);

        //One mark every 5'ish degrees
        var count = (int) Math.Floor (SweepAngle / 5) + 1;
        var pts = new PointF[2 + count];
        pts[0] = new PointF (0, 0);
        pts[1] = new PointF (radius, 0);
        var angle = 0.0;
        for (var j = 2; j < count + 2; j++)
        {
            angle += SweepAngle / count;

            pts[j] = new PointF
                (
                    radius * (float) Math.Cos (angle * Math.PI / 180.0),
                    radius * (float) Math.Sin (angle * Math.PI / 180.0)
                );
        }

        matrix.TransformPoints (pts);

        coords = $"{pts[0].X:f0},{pts[0].Y:f0},{pts[1].X:f0},{pts[1].Y:f0},";
        for (var j = 2; j < count + 2; j++)
        {
            coords += string.Format (j > count ? "{0:f0},{1:f0}" : "{0:f0},{1:f0},", pts[j].X, pts[j].Y);
        }

        return true;
    }

    #endregion
}
